package de.hauschild.dbc4j.util;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;

/**
 * @author Klaus Hauschild
 * @since 1.0.0
 */
public class ReflectionUtils {

  private static Map<Class<?>, Set<Annotation>> classAnnotations = new HashMap<Class<?>, Set<Annotation>>();
  private static Map<Method, Set<Annotation>> methodAnnotations = new HashMap<Method, Set<Annotation>>();

  /**
   * Returns the annotations for the given class object from itself, its super-class and all implemented interfaces.
   * 
   * @param clazz
   *          the class
   * @return the annotations
   */
  public static Set<Annotation> getAnnotations(final Class<?> clazz) {
    if (clazz == null) {
      return Collections.emptySet();
    }

    final Set<Annotation> cachedAnnotations = classAnnotations.get(clazz);
    if (cachedAnnotations != null) {
      return cachedAnnotations;
    }

    final Set<Annotation> annotations = new HashSet<Annotation>();

    // own annotations
    annotations.addAll(Arrays.asList(clazz.getAnnotations()));

    // annotations from super-class
    annotations.addAll(getAnnotations(clazz.getSuperclass()));

    // annotations from super-interfaces
    for (final Class<?> interfaze : clazz.getInterfaces()) {
      annotations.addAll(getAnnotations(interfaze));
    }

    // store annotations
    classAnnotations.put(clazz, annotations);

    return annotations;
  }

  public static Set<Annotation> getAnnotations(final Method method) {
    if (method == null) {
      return Collections.emptySet();
    }

    final Set<Annotation> cachedAnnotations = methodAnnotations.get(method);
    if (cachedAnnotations != null) {
      return cachedAnnotations;
    }

    final Set<Annotation> annotations = new HashSet<Annotation>();

    // own annotations
    annotations.addAll(Arrays.asList(method.getAnnotations()));

    // annotations from super-class
    final Class<?> superClass = method.getDeclaringClass().getSuperclass();
    if (superClass != null) {
      annotations.addAll(getAnnotations(getMethod(superClass, method.getName(), method.getParameterTypes())));
    }

    // annotations from super-interfaces
    for (final Class<?> interfaze : method.getDeclaringClass().getInterfaces()) {
      annotations.addAll(getAnnotations(getMethod(interfaze, method.getName(), method.getParameterTypes())));
    }

    // store annotations
    methodAnnotations.put(method, annotations);

    return annotations;
  }

  public static Method getMethod(final Class<?> clazz, final String methodName, final Class<?>... parameterTypes) {
    final Method method = getMethodByName(clazz, methodName);
    return method;
  }

  private static Method getMethodByName(final Class<?> clazz, final String methodName) {
    final List<Method> methodWithSameName = new ArrayList<Method>(Arrays.asList(clazz.getMethods()));
    CollectionUtils.filter(methodWithSameName, new Predicate() {
      public boolean evaluate(final Object theObject) {
        return ((Method) theObject).getName().equals(methodName);
      }
    });

    if (methodWithSameName.size() == 1) {
      return methodWithSameName.get(0);
    }

    return null;
  }

  /**
   * Do not instantiate this class.
   */
  private ReflectionUtils() {
    // nothing to do
  }

}
